#include "./ota.h"
#include "../utils/store.h"
#include "../device/compat.h"

namespace service::ota {

using namespace driver;
using Error = utils::error_t;
namespace ec = utils::ec;
namespace proto = utils::proto;

struct Info {
    uint32_t OtaPkgSize;
    uint32_t AppAddr;
    uint32_t OtaAddr;

    void OtaInit(uint32_t pkgsize) {
        OtaPkgSize = pkgsize;
        AppAddr = flash::AppAddr;
        OtaAddr = flash::OtaAddr;
    }

    bool Verify() const {
        return  (OtaPkgSize > 0) &&
                (OtaPkgSize < flash::AppSize) &&
                (AppAddr == flash::AppAddr) &&
                (OtaAddr == flash::OtaAddr);
    }
};

utils::Store<Info, flash::OtaInfoAddr> info;

struct Context {
    uint32_t pkg_size;
    uint16_t pkg_id;
    uint32_t pkg_index;

    void init(uint32_t size) {
        pkg_size = size;
        pkg_id = 0;
        pkg_index = 0;
    }
};

static Context ctx;

void on_start(const message_t &msg) {
    uint32_t size;
    Error err = msg.parse(size);
    if (err) {
        goto finish;
    }
    if ((size == 0) || (size > flash::AppSize)) {
        err = ec::ArgInvalid;
        goto finish;
    }
    if (!flash::erase(flash::OtaAddr, size)) {
        err = ec::FlashEraseFail;
        goto finish;
    }
    ctx.init(size);
finish:
    msg.write(proto::OTAStart, err);
}

void on_translate(const message_t &msg) {
    uint16_t id;
    utils::byte_view_t bw;
    Error err = msg.parse(id, bw);
    if (err) {
        goto finish;
    }
    if (id != ctx.pkg_id) {
        err = ec::OtaIdInvalid;
        goto finish;
    }
    if (!flash::write(flash::OtaAddr + ctx.pkg_index, bw.buf, bw.len)) {
        err = ec::FlashFail;
        goto finish;
    }
    ctx.pkg_index += bw.len;
    ctx.pkg_id ++;
finish:
    msg.write(proto::OTATranslate, err);
}

void on_complete(const message_t &msg) {
    utils::byte_view_t bw;
    uint8_t m5[16];
    Error err = msg.parse(bw);
    if (err) {
        goto finish;
    }
    if (ctx.pkg_size != ctx.pkg_index) {
        err = ec::ArgInvalid;
        goto finish;
    }
    if (bw.len != 16) {
        err = ec::OtaMd5SumFail;
        goto finish;
    }
    utils::md5::Sum(reinterpret_cast<const void *>(flash::OtaAddr), ctx.pkg_size, m5);
    if (memcmp(m5, bw.buf, 16) != 0) {
        err = ec::OtaMd5SumFail;
        goto finish;
    }
    info->OtaInit(ctx.pkg_size);
    err = info.save();
finish:
    msg.write(proto::OTAComplete, err);
    if (err) {
        return;
    }
    osDelay(500);
    NVIC_SystemReset();
}

}

